using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace GP2D3D_Homework4_RickBeijer_S1012261
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Game
    {
        private GraphicsDeviceManager graphicsDeviceManager;
        private SpriteBatch spriteBatch;
        private bool isKeyEscapeDown;
        private TimeSpan elapsedUpdateTime;
        private int totalFrames;
        private int framerate;
        private SpriteFont framerateFont;
        private Model cube;
        private List<Model> teapots;
        private BasicEffect cubeBasicEffect;
        private BasicEffect teapotBasicEffect;
        private Matrix cubeScaling;
        private Vector3 cubeColor;
        private List<Vector3> teapotColors;
        private Matrix generalStepRotation;
        private Matrix teapotRotation;
        private Matrix teapotStepRotation;
        private List<Matrix> teapotRotations;
        private Matrix cubeTranslation;
        private Matrix teapotTranslation;

        public Game1()
        {
            this.graphicsDeviceManager = new GraphicsDeviceManager( this );
            this.Content.RootDirectory = "Content";

            this.IsMouseVisible = true;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.cubeBasicEffect = new BasicEffect( this.GraphicsDevice, null );
            this.cubeBasicEffect.World = Matrix.Identity;
            this.cubeBasicEffect.View = Matrix.CreateLookAt( new Vector3( 3, 3, 10 ), Vector3.Zero, Vector3.Up );
            this.cubeBasicEffect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians( 45 ),
                                                                                   (float) this.Window.ClientBounds.Width /
                                                                                   (float) this.Window.ClientBounds.Height,
                                                                                   0.1f,
                                                                                   20f );

            this.teapotBasicEffect = new BasicEffect( this.GraphicsDevice, null );
            this.teapotBasicEffect.World = Matrix.Identity;
            this.teapotBasicEffect.View = this.cubeBasicEffect.View;
            this.teapotBasicEffect.Projection = this.cubeBasicEffect.Projection;

            this.cubeScaling = Matrix.CreateScale( 2.0f );

            this.cubeColor = Color.AliceBlue.ToVector3();
            this.teapotColors = new List<Vector3>();
            this.teapotColors.Add( Color.AntiqueWhite.ToVector3() );
            this.teapotColors.Add( Color.Aqua.ToVector3() );
            this.teapotColors.Add( Color.Aquamarine.ToVector3() );
            this.teapotColors.Add( Color.Azure.ToVector3() );
            this.teapotColors.Add( Color.Beige.ToVector3() );
            this.teapotColors.Add( Color.Bisque.ToVector3() );

            this.generalStepRotation = Matrix.CreateRotationZ( MathHelper.ToRadians( 1.0f ) ) *
                                       Matrix.CreateRotationY( MathHelper.ToRadians( 1.0f ) ) *
                                       Matrix.CreateRotationX( MathHelper.ToRadians( 1.0f ) );
            this.teapotRotation = Matrix.Identity;
            this.teapotStepRotation = Matrix.CreateRotationY( MathHelper.ToRadians( -2.0f ) );
            this.teapotRotations = new List<Matrix>();
            this.teapotRotations.Add( Matrix.CreateRotationZ( MathHelper.ToRadians( 90.0f ) ) );
            this.teapotRotations.Add( Matrix.CreateRotationZ( MathHelper.ToRadians( -90.0f ) ) );
            this.teapotRotations.Add( Matrix.CreateRotationZ( MathHelper.ToRadians( 0.0f ) ) );
            this.teapotRotations.Add( Matrix.CreateRotationZ( MathHelper.ToRadians( 180.0f ) ) );
            this.teapotRotations.Add( Matrix.CreateRotationX( MathHelper.ToRadians( 90.0f ) ) );
            this.teapotRotations.Add( Matrix.CreateRotationX( MathHelper.ToRadians( -90.0f ) ) *
                                      Matrix.CreateRotationZ( MathHelper.ToRadians( 90.0f ) ) );

            this.cubeTranslation = Matrix.CreateTranslation( new Vector3( 0, -0.5f, 0 ) );
            this.teapotTranslation = Matrix.CreateTranslation( new Vector3( 0, 1f, 0 ) );

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            this.spriteBatch = new SpriteBatch( this.GraphicsDevice );

            this.framerateFont = this.Content.Load<SpriteFont>( "FramerateFont" );
            this.cube = this.Content.Load<Model>( "cube" );
            this.teapots = new List<Model>();
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
            this.teapots.Add( this.Content.Load<Model>( "teapot" ) );
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update( GameTime gameTime )
        {
            // Allows the game to exit
            if ( Keyboard.GetState().IsKeyUp( Keys.Escape ) &&
                 this.isKeyEscapeDown )
            {
                this.Exit();
            }

            this.isKeyEscapeDown = Keyboard.GetState().IsKeyDown( Keys.Escape );

            this.elapsedUpdateTime += gameTime.ElapsedGameTime;
            this.totalFrames++;
            bool shouldUpdate = false;

            if ( this.elapsedUpdateTime.Seconds > 0 )
            {
                this.elapsedUpdateTime = TimeSpan.Zero;
                this.framerate = this.totalFrames;
                this.totalFrames = 0;
                shouldUpdate = true;
            }

            var random = new Random();

            if ( shouldUpdate )
            {
                this.cubeBasicEffect.World *= this.generalStepRotation;
                this.teapotBasicEffect.World *= this.generalStepRotation;
                this.teapotRotation *= this.teapotStepRotation;
            }

            for ( int counter = 0; counter < this.teapotColors.Count; counter++ )
            {
                this.teapotColors[counter] = new Vector3( (float) random.NextDouble(),
                                                          (float) random.NextDouble(),
                                                          (float) random.NextDouble() );
            }

            base.Update( gameTime );
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw( GameTime gameTime )
        {
            this.GraphicsDevice.Clear( Color.CornflowerBlue );

            this.GraphicsDevice.RenderState.CullMode = CullMode.None;
            this.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

            foreach ( ModelMesh cubeMesh in this.cube.Meshes )
            {
                foreach ( BasicEffect cubeEffect in cubeMesh.Effects )
                {
                    cubeEffect.World = this.cubeTranslation *
                                       this.cubeScaling *
                                       this.cubeBasicEffect.World;
                    cubeEffect.View = this.cubeBasicEffect.View;
                    cubeEffect.Projection = this.cubeBasicEffect.Projection;

                    cubeEffect.LightingEnabled = true;
                    cubeEffect.PreferPerPixelLighting = true;
                    cubeEffect.DiffuseColor = Color.White.ToVector3();
                    cubeEffect.DirectionalLight0.Enabled = true;
                    cubeEffect.DirectionalLight0.DiffuseColor = this.cubeColor;
                    cubeEffect.DirectionalLight0.SpecularColor = this.cubeColor;
                }

                cubeMesh.Draw();
            }

            this.GraphicsDevice.RenderState.FillMode = FillMode.Solid;

            int counter = 0;
            foreach ( Model teapot in this.teapots )
            {
                foreach ( ModelMesh teapotMesh in teapot.Meshes )
                {
                    foreach ( BasicEffect teapotEffect in teapotMesh.Effects )
                    {
                        teapotEffect.World = this.teapotRotation *
                                             this.teapotTranslation *
                                             this.teapotRotations[counter] *
                                             this.teapotBasicEffect.World;
                        teapotEffect.View = this.teapotBasicEffect.View;
                        teapotEffect.Projection = this.teapotBasicEffect.Projection;

                        teapotEffect.LightingEnabled = true;
                        teapotEffect.PreferPerPixelLighting = true;
                        teapotEffect.DiffuseColor = Color.White.ToVector3();
                        teapotEffect.DirectionalLight0.Enabled = true;
                        teapotEffect.DirectionalLight0.Direction = Vector3.Down;
                        teapotEffect.DirectionalLight0.DiffuseColor = this.teapotColors[counter];
                        teapotEffect.DirectionalLight0.SpecularColor = teapotEffect.DirectionalLight0.DiffuseColor;
                    }

                    teapotMesh.Draw();
                }

                counter++;
            }

            // For proper non-see-through teapots:
            this.spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);
            // For ghost teapots:
            //this.spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.FrontToBack, SaveStateMode.None);
            this.spriteBatch.DrawString(this.framerateFont, this.framerate + " FPS", new Vector2(725, 575), Color.OliveDrab);
            this.spriteBatch.End();

            base.Draw( gameTime );
        }
    }
}